package com.yupi.yupicturebackend.manager.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.yupi.yupicturebackend.manager.websocket.disruptor.PictureEditEventProducer;
import com.yupi.yupicturebackend.manager.websocket.model.PictureEditActionEnum;
import com.yupi.yupicturebackend.manager.websocket.model.PictureEditMessageTypeEnum;
import com.yupi.yupicturebackend.manager.websocket.model.PictureEditRequestMessage;
import com.yupi.yupicturebackend.manager.websocket.model.PictureEditResponseMessage;
import com.yupi.yupicturebackend.model.entity.User;
import com.yupi.yupicturebackend.model.vo.UserVO;
import com.yupi.yupicturebackend.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class PictureEditHandler extends TextWebSocketHandler {

    @Resource
    private UserService userService;

    @Resource
    private PictureEditEventProducer pictureEditEventProducer;

    // 每张图片的编辑状态 key:pictureId value:userId 当前正在编辑的用户ID
    private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>();

    // 每张图片所有连接的会话 key:pictureId value: 用户会话集合
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>();
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 保存会话到集合中
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
        pictureSessions.get(pictureId).add(session);

        // 构造响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        UserVO userVO = userService.getUserVO(user);
        pictureEditResponseMessage.setUser(userVO);
        String message = String.format("用户 %s 进入编辑", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        // 广播给同一照片的用户
        broadcastToPicture(pictureId, pictureEditResponseMessage);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 将消息转化为 pictureEditMessage
        PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class);

        // 从 session 中获取公共参数
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        pictureEditEventProducer.publishEvent(pictureEditRequestMessage, session, user, pictureId);

    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");

        // 移除当前用户编辑状态
        handleExitEditMessage(null, pictureId, user, session);

        // 删除会话
        if (pictureSessions.containsKey(pictureId)){
            Set<WebSocketSession> socketSessionSet = pictureSessions.get(pictureId);
            if (CollUtil.isNotEmpty(socketSessionSet)){
                socketSessionSet.remove(session);
            }
            if (socketSessionSet.isEmpty()){
                pictureSessions.remove(pictureId);
            }
        }
        // 构造响应信息
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
        pictureEditResponseMessage.setMessage("用户 " + user.getUserName() + " 退出编辑");
        pictureEditResponseMessage.setUser(userService.getUserVO(user));
        broadcastToPicture(pictureId, pictureEditResponseMessage);

    }


    public void handleExitEditMessage(PictureEditRequestMessage pictureEditRequestMessage, Long pictureId, User user, WebSocketSession session) throws JsonProcessingException {
        Long editingUser = pictureEditingUsers.get(pictureId);

        if (editingUser.equals(user.getId())){
            pictureEditingUsers.remove(pictureId);
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
            pictureEditResponseMessage.setMessage("用户" + user.getUserName() + "退出编辑状态");
            pictureEditResponseMessage.setUser(userService.getUserVO(user));
            broadcastToPicture(pictureId, pictureEditResponseMessage);
        }

    }

    public void handleEditActionMessage(PictureEditRequestMessage pictureEditRequestMessage, Long pictureId, User user, WebSocketSession session) throws JsonProcessingException {
        Long editingUser = pictureEditingUsers.get(pictureId);
        String editAction = pictureEditRequestMessage.getEditAction();
        PictureEditActionEnum editActionEnum = PictureEditActionEnum.getEnumByValue(editAction);

        if (editActionEnum == null){
            return;
        }

        if (editingUser != null && user.getId().equals(editingUser)){
            // 确保只有进入编辑状态的用户可以编辑
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue());
            String message = String.format("%s 执行 %s", user.getUserName(), editActionEnum);
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setEditAction(editAction);
            pictureEditResponseMessage.setUser(userService.getUserVO(user));
            // 除当前进入编辑状态的用户可以接收到信息
            broadcastToPicture(pictureId, pictureEditResponseMessage, session);
        }
    }

    /**
     * 开始编辑处理方法
     * @param pictureEditRequestMessage
     * @param pictureId
     * @param user
     * @param session
     * @throws IOException
     */
    public void handleEnterEditMessage(PictureEditRequestMessage pictureEditRequestMessage, Long pictureId, User user, WebSocketSession session) throws IOException {
        if (!pictureEditingUsers.containsKey(pictureId)){
            // 设置当前用户进入该图片的编辑状态
            Long userId = user.getId();
            pictureEditingUsers.put(pictureId, userId);
            // 向其它用户发送消息
            String format = String.format("用户 %s 开始编辑", user.getUserName());
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
            pictureEditResponseMessage.setMessage(format);
            pictureEditResponseMessage.setUser(userService.getUserVO(user));
            broadcastToPicture(pictureId, pictureEditResponseMessage);

        }
    }

    /**
     * 广播消息的方法，用于向图片链接的所有会话发送消息，由于可能不需要向进入图片编辑状态的用户发送消息，额外接收一个参数排除该用户的 websocketSession
     * @param pictureId
     * @param pictureEditResponseMessage
     * @param excludeSession
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws JsonProcessingException {
        Set<WebSocketSession> socketSessionSet = pictureSessions.get(pictureId);
        if (ObjUtil.isNotEmpty(socketSessionSet)){
            // 由于精度缺失问题，需要对 long 类型的数字转化为字符串，需要向 ObjectMapper 中添加 module
            ObjectMapper objectMapper = new ObjectMapper();
            // 配置序列化
            SimpleModule module = new SimpleModule();
            module.addSerializer(Long.class, ToStringSerializer.instance); // 支持包装类
            module.addSerializer(Long.TYPE, ToStringSerializer.instance); // 支持基本类型
            objectMapper.registerModule(module);
            // 序列化为 JSON 字符串
            String message = objectMapper.writeValueAsString(pictureEditResponseMessage);
            TextMessage textMessage = new TextMessage(message);
            for (WebSocketSession session : socketSessionSet) {
                if (excludeSession != null && session.equals(excludeSession)){
                    continue;
                }
                try {
                    if (session.isOpen()){
                        session.sendMessage(textMessage);
                    }
                } catch (IOException e) {
                    log.error("消息发送失败， {}", session);
                    e.printStackTrace();
                }
            }
        }
    }

    private void broadcastToPicture(Long pictureId,PictureEditResponseMessage pictureEditResponseMessage) throws JsonProcessingException {
        broadcastToPicture(pictureId, pictureEditResponseMessage, null);
    }
}
